'use strict';
/**
 * a factory to generate controller and actions
 * The reason is we want to pre-map all of this available
 * function for a fast look up, and enable us to use a common pattern
 * to serve up the app
 * singature
 * module.exports = Controller(app).factory('api' , {
       'index': function(req , res , next)
       {
			 		// default get method
       },
			 'auth': { // doesn't it really matter? since we are catch all, that means including the methods as well
			 		method: 'post', // specify how this url will be serve
					handler: function(req , res , next)
					{

					}
		 	 }
 	 });
 */

var _ = require('lodash'),
	path = require('path'),
	// multer = require('multer'), // for file upload
	helper = require('./methods');
/**
 * export interface and init the Controller at this point
 * @param (object) express app
 * @param (mixed) optional whether to protect the entire controller path
 */

// a really dumb way to maintain compatiblity
if (!_.isFunction(_.get)) {
	_.get = function(obj , path)
	{
		if (path.indexOf('.') > -1) {
			path.split('.').forEach(function(p) {
				obj = obj[p];
			});
			return obj;
		}
		return obj[path];
	};
}

/**
 * Controller implementation
 */
var Controller = function(app , models , protect)
{
	// property to hold all the mapped objects
	this.map = {};
	this.actions = {};
	this.models = models;
	// only support true boolean type here
	this.protectAll = (_.isBoolean(protect)) ? protect : false;
	// now the app is useful for retrieving the other modules
	this.app = app;
	/**
	 * we want to extend this method to have new functionality
	 * basically this is currying the action
	 * @param (string) name of action
	 * @param (function) the action defintion
	 * @return (function)
	 */
	this.decorate = function(actionName , actionHandler)
	{
		var self = this;
		// same as the router will call back with the same parameters
		// 19.8.2015 - we add a params to the last as placeholder
		return function(req , res , next , params)
		{
			actionHandler.prototype.controller = self;
			actionHandler.prototype.actionName = actionName;
			actionHandler.prototype.req        = req;
			actionHandler.prototype.res        = res;
			actionHandler.prototype.next       = next;
			actionHandler.prototype.respond    = 'render';
			actionHandler.prototype.template   = 'index';
			/**
			 * we do extensive check here in case wrong typing
			 * @param (string) property name
			 * @param (mixed) value to assign to property
			 * @return undefined
			 */
			actionHandler.prototype.config = function(key , val)
			{
				if (helper.exists(['respond' , 'template' , 'res' , 'tpl'] , key)) {
					switch (key) {
						case 'respond':
						case 'res':
							if (helper.exists(helper.expressOutputMethods , val)) {
								this.respond = val;
							}
						break;
						case 'template':
						case 'tpl':
							if (_.isString(val)) {
								this.template = val;
							}
						break;
					}
				}
				return this; // allow chaining
			};
			/**
			 * return those parameters we collected during the last middleware (requestHandler)
			 * @param (string) optional if you specify a search path
			 */
			actionHandler.prototype.params = function(path)
			{
				var _params = this.res.locals.panes.params;
				if (path && _.isString(path)) {
					if (path.indexOf('.')) {
						// this is really dump _.get is not a function! See above
						return _.get(_params , path);
					}
					return _params[path];
				}
				return _params;
			};

			/**
			 * TODO: generate a file uplaod handler based on multer // http://npm.taobao.org/package/multer
			 * TODO: add external CDN storage
			 */
			actionHandler.prototype.upload = function()
			{
				// example
				var storage = multer.diskStorage({
					destination: function (req, file, cb) {
						cb(null, '/tmp/my-uploads')
					},
					filename: function (req, file, cb) {
						cb(null, file.fieldname + '-' + Date.now())
					}
				});
				var upload = multer({ storage: storage });
			};
			/**
			 * encap two steps in one method, and it must be the last to call
			 * @param (mixed)
			 * @param (string) the different template to call
			 * @param (string) what kind of method to call
			 * @return undefined
			 */
			actionHandler.prototype.set = function(data)
			{
				var _this = this;
				var local = {
							data: data,
							respond: _this.respond,
							template: _this.template,
							action: _this.actionName
						};

				if (this.res.locals.panes) {
					this.res.locals.panes = _.merge(this.res.locals.panes , local);
				}
				else {
					this.res.locals.panes = local;
				}
				this.next();
			};

			/**
			 * get the model to use
			 */
			actionHandler.prototype.use = function(modelName)
			{
				return this.controller.models[modelName];
			};
			//////////////////////////////
			//  SPECIAL OUTPUT METHODS  //
			//////////////////////////////

			/**
			 * basically its a wrapper short hand
			 * @param (mixed) data
			 */
			actionHandler.prototype.json = function(data)
			{
				this.config('respond' , 'json').set(data);
			};
			/**
			 * @param (mixed) data
			 * @param (string) a different jsonp callback name (NEED TEST)
			 */
			actionHandler.prototype.jsonp = function(data , callbackName)
			{
				if (callbackName && _.isString(callbackName)) {
					this.res.app.set('jsonp callback name' , callbackName);
				}
				this.config('respond' , 'jsonp').set(data);
			}
			/**
			 * @param (mixed) data
			 * @param (function) a function to parse the data to xml format
			 */
			actionHandler.prototype.xml = function(data , xmlFormatter)
			{
				if (data && _.isFunction(xmlFormatter)) {
					this.config('respond' , 'xml').set(xmlFormatter(data));
				}
				else {
					throw 'xml expect first parameter to be an object or array. And second parameter to be a function!';
				}
			};
			// @return
			// init the new instance
			return new actionHandler(req , res , next , params);
		};
	};

	/**
	 * getter method to get back the action
	 * and we init it here, the wrapping function is for
	 * connecting this interface with the express app
	 */
	this.action = function(actionName)
	{
		var _this = this;
		return function(req , res , next , params) {
			// do I need to call this new again here?
			_this.actions[actionName](req , res , next , params);
			return _this; // return itself
		};
	};

	/**
	 * the main method to map out the path to an action
	 * 19.8.2015 - this whole router.param thing is so messy so we are not going to support here
	 * obj.params = _param_;
	 * add one extra map
	 * _this.map[_param_] = _.merge({}  , obj , {params: _param_});
	 */
	this.factory = function(controllerName, actions)
	{
		var _this = this;
		_this.controllerName = controllerName;
		// new allow single part path @TODO need to test it
		if (_.isFunction(actions)) {
			actions = {'/' : actions};
		}
		_.forIn(actions , function(_action , _actionName)
		{
			var _protect = _this.protectAll,
				_listen  = 'all',
				_params;
			/**************************************
				15.9.2015
				we are changing the way how we handle
				the action construction here to deal
				with the chicken and egg issue
			***************************************/
			if (_.isFunction(_action)) {
				_this.actions[_actionName] = _this.decorate(_actionName , _action);
			}
			else if (_.isPlainObject(_action)) {
				if (_.isUndefined(_action.action) || _.isNull(_action.action) || !_.isFunction(_action.action)) {
					throw 'Your action defintion is unsupported or missing!';
				}
				_this.actions[_actionName] = _this.decorate(_actionName , _action.action);
				// this way we allow the action level to change the upper level
				if (!_.isUndefined(_action.protect) && !_.isNull(_action.protect)) {
					_protect = _action.protect;
				}
				// again finer control how each route get handle
				if (!_.isUndefined(_action.listen) && !_.isNull(_action.listen)) {
					_listen = _action.listen;
				}
				// construct url like /controller/action/:param/:id
				if (!_.isUndefined(_action.params) && !_.isNull(_action.params)) {
					if (_.isString(_action.params)) { // let the user figure our how to deal with this
						_params = [_action.params];
					}
					else if (_.isArray(_actions.params)) {
						_params = _action.params;
					}
				}
			}
			else {
				throw 'Unrecognized parameter for Controller.factory! We only support Function or Object.';
			}
			var routePaths = (_actionName==='/') ? [_this.controllerName] : [_this.controllerName , _actionName],
				obj = {controller: _this.controllerName,
						   action: _actionName ,
				    userGenerated: false ,
						  protect: _protect ,
						   listen: _listen};
			if (!_.isUndefined(_params)) {
				_params.forEach(function(_param_)
				{
					routePaths.push(':' + helper.replaceAll(_param_ , ':' , ''));
				});
			}
			// we add our own style of more flexible route here
			_this.map[ '/' + path.join.apply(path , routePaths) ] = obj;
		});
		// we create a name space to hold all our own application params
		var routeMap = _this.app.get('routeMap') || {};
		routeMap = _.merge(routeMap , this.map);
		_this.app.set('routeMap' , routeMap);

		return this;
	};
};

// export it
module.exports = Controller;
// -- EOF --
